package com.pz.gulimall.product.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.common.utils.UuidUtils;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.pz.gulimall.product.vo.res.CategoryOptionResVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pz.gulimall.common.utils.PageUtils;
import com.pz.gulimall.common.utils.Query;

import com.pz.gulimall.product.dao.CategoryDao;
import com.pz.gulimall.product.entity.CategoryEntity;
import com.pz.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private CacheService cacheService;
    @Resource
    private CategoryBrandRelationServiceImpl categoryBrandRelationService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    public List<CategoryEntity> getCategoriesFromDBWithRedisLock(){
        String uuid = UuidUtils.generateUuid();
        String lockKey = "lock";
        //加锁（获取锁）
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 300, TimeUnit.SECONDS);
        if(Boolean.TRUE.equals(lock)){
            //加锁成功获取到锁
            String categoriesJson = redisTemplate.opsForValue().get("categoriesJSON");
            List<CategoryEntity> ans;
            try {
                if(!StringUtils.isEmpty(categoriesJson)){
                    ans = JSON.parseArray(categoriesJson, CategoryEntity.class);
                } else {
                    log.info("缓存不命中，查询数据库...");
                    ans = baseMapper.selectList(null);
                    Random random = new Random();
                    int timeout = random.nextInt(3)+1;
                    redisTemplate.opsForValue().set("categoriesJSON", JSON.toJSONString(ans), timeout, TimeUnit.DAYS);
                }
            }finally {
                //Lua 脚本删除锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return  redis.call('del', KEYS[1]) else return 0 end";
                Long delLockRes = redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class)
                , Collections.singletonList(lockKey), uuid);
            }
            return ans;
        } else {
            //没获取到锁，继续获取，自旋加锁
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                log.error("redis分布式异常:{}--{}",e.getClass(), e.getMessage());
            }
            return getCategoriesFromDBWithRedisLock();
        }
    }

    public List<CategoryEntity> getCategoriesFromDBWithLocalLock(){
        //得到锁后，再去缓存中确定一次，如果没有再去查
        String categoriesJson = redisTemplate.opsForValue().get("categoriesJSON");
        List<CategoryEntity> entireList;
        if(!StringUtils.isEmpty(categoriesJson)){
            entireList = JSON.parseArray(categoriesJson, CategoryEntity.class);
        } else {
            synchronized (this) {
                // 存json，添加过期时间，时间随机，加锁
                log.info("缓存不命中，查询数据库...");
                entireList = baseMapper.selectList(null);
                Random random = new Random();
                int timeout = random.nextInt(3)+1;
                redisTemplate.opsForValue().set("categoriesJSON", JSON.toJSONString(entireList), timeout, TimeUnit.DAYS);
            }
        }
        return entireList;
    }



    /**
     * 查询所有分类，并将其组装为父子解构(pms_category表)
     */
    @Override
    @Cacheable(value = "categories", key = "#root.methodName")
    public List<CategoryEntity> treeList(){
        List<CategoryEntity> entireList = cacheService.getCategoriesWithSpringCache();

        //组装
        //1、找到所有一级数据
        List<CategoryEntity> leve1 = entireList.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu)->{
            //2、递归查找子树
            menu.setChildList(getChildren(menu, entireList));
            return menu;
        }).sorted((menu1, menu2)->{
            //3、同层排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        //

        return leve1;
    }

    /**
     * 递归查找子菜单
     * @param current 当前菜单
     * @param all 所有菜单
     * @return
     */
    private List<CategoryEntity> getChildren(CategoryEntity current, List<CategoryEntity> all){
        List<CategoryEntity> children = all.stream().filter((categoryEntity -> {
            // 过滤 出所有的子菜单
            return categoryEntity.getParentCid().longValue() == current.getCatId().longValue();
        })).map(categoryEntity -> {
            //递归查找
            categoryEntity.setChildList(getChildren(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }


    public Long[] findCatelogIdPath(Long catelogId){
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findCatelogIdPath(catelogId, paths);
        Long[] res = new Long[parentPath.size()];
        for (int i = 0; i < parentPath.size(); i++) {
            res[i] = parentPath.get(parentPath.size()-1-i);
        }
        return res;
    }

    /**
     *
     * @param catelogId
     * @param paths  搜集路径
     * @return
     */
    private List<Long> findCatelogIdPath(Long catelogId, List<Long> paths){
        paths.add(catelogId);
        CategoryEntity category = getById(catelogId);
        if(category.getParentCid() != 0){
            findCatelogIdPath(category.getParentCid(), paths);
        }
        return paths;
    }

    @Override
    @CacheEvict(value = "categories", allEntries = true)
    @Transactional
    public void updateCategory(CategoryEntity category) {
        this.getBaseMapper().updateById(category);
        categoryBrandRelationService.updateWhenCategoryChange(category);
    }

    /**
     *
     * @param catId 三级分类ID
     * @return catId所属的数，以一个一级分类为根节点
     */
    @Override
    public CategoryOptionResVo getOptionsByCatId(Long catId) {
        //先搜索出路径
        CategoryOptionResVo vo = new CategoryOptionResVo();
        List<CategoryEntity> tree = treeList();
        Long[] path = findCatelogIdPath(catId);
        List<CategoryEntity> collect = tree.stream().filter(categoryEntity -> categoryEntity.getCatId().longValue() == path[0]).collect(Collectors.toList());
        if(!collect.isEmpty()){
            CategoryEntity root = collect.get(0);
            vo.setLevel1(root);
            Queue<CategoryEntity> que = new ArrayDeque<>();
            que.add(root);
            int leve = 0;
            while (!que.isEmpty()){
                int count = que.size();
                leve++;
                List<CategoryEntity> list = new ArrayList<>();
                for (int i = 0; i <count ; i++) {
                    CategoryEntity front = que.poll();
                    if(front.getChildList() !=null && !front.getChildList().isEmpty()){
                        que.addAll(front.getChildList());
                    }
                    if(leve > 1){
                        list.add(front);
                    }
                }
                if(leve == 2){
                    vo.setLevel2(list);
                }
                if(leve == 3){
                    vo.setLevel3(list);
                }
            }
        }
        return vo;
    }

}